import "../../lib/ramda.js";
import TABLE from "./table.js";

const _ = {
    PIECE_BLACK: -1,
    PIECE_WHITE: 1,
    PIECE_EMPTY: 0,
    BOARD_SIZE: 10,
    BOARD_LEN: 100,
    INIT_PIECES:[
        [44, -1],
        [45, 1],
        [54, 1],
        [55, -1],
    ],
    loc_in_range(loc) {
        return ((loc >= 0) && (loc < _.BOARD_LEN))
    },
    piece_to_str(p){
        return ((p == _.PIECE_EMPTY) ? '-' : ((p == _.PIECE_BLACK) ? 'b' : 'w'))
    },
    piece_from_str(str){
        return ((str == '-') ? _.PIECE_EMPTY : ((str == 'b') ? _.PIECE_BLACK : _.PIECE_WHITE))
    },
    arr_from_str(str){
        const arr = new Int8Array(_.BOARD_LEN)
        let fill_i = 0
        const units = str.split('|')
        for (const part of units) {
            const p = _.piece_from_str(part[0])
            const n = Number(part.slice(1))
            arr.fill(p, fill_i, fill_i+n)
            fill_i += n
        }
        return arr
    },
    arr_to_str(arr){
        const units = []
        let last = arr[0]
        let n_rept = 0
        for (const i in arr) {
            const p = arr[i]
            if (p == last) {
                n_rept += 1
            } else {
                units.push(`${_.piece_to_str(last)}${n_rept}`)
                last = arr[i]
                n_rept = 1
            }
        }
        units.push(`${_.piece_to_str(last)}${n_rept}`)
        return units.join('|')
    },
}

class Board {
    constructor(_from=undefined) {
        this._arr = new Int8Array(R.is(String,_from) ? _.arr_from_str(_from) : (R.is(Board,_from) ? _from._data() : _.BOARD_LEN))
    }
    _data(){
        return this._arr
    }

    reset(_from=undefined) {
        if (R.is(Int8Array, _from)) {
            this._arr.set(_from)
        } else if (R.is(Board, _from)) {
            this._arr.set(_from._arr)
        } else {
            this._arr.fill(_.PIECE_EMPTY)
            for (const init of _.INIT_PIECES) {
                this._arr[init[0]] = init[1]
            }
        }
        return this
    }

    to_str() {
        return _.arr_to_str(this._arr)
    }

    n_black() {
        return R.filter(R.equals(_.PIECE_BLACK), this._arr).length
    }

    n_white() {
        return R.filter(R.equals(_.PIECE_WHITE), this._arr).length
    }

    max_piece() {
        let n_black = 0
        let n_white = 0
        for (const i in this._arr) {
            const p = this._arr[i]
            if (p == _.PIECE_BLACK) {n_black += 1}
            else if (p == _.PIECE_WHITE) {n_white += 1}
        }
        if (n_black == n_white) {return _.PIECE_EMPTY}
        else if (n_black < n_white) {return _.PIECE_WHITE}
        else {return _.PIECE_BLACK}
    }

    see(loc) {
        return this._arr[loc]
    }
    _set(loc, p) {
        this._arr[loc] = p
        return this
    }

    can_move(pcur, loc) {
        const arr = this._arr
        if (arr[loc] != _.PIECE_EMPTY) {return false}
        const order_map = [1, 0, 2]
        const side = order_map[(-pcur) + 1]
        function _on_way(lt, i, n=0) {
            const loc = lt[i]
            const p = arr[loc]
            const p_map = order_map[p+1]
            if (((TABLE.SWAP_LOC_MASK[loc][i] * side) & (p_map & side)) == _.PIECE_EMPTY) {
                return (p_map & order_map[pcur+1]) * n
            }
            return _on_way(TABLE.SWAP_LOC_TABLE[loc], i, n+1)
        }
        const center = TABLE.SWAP_LOC_TABLE[loc]
        let n_all = 0
        for (const i_lt in center) {
            const n = _on_way(center, i_lt)
            n_all += n
        }
        return n_all > 0
    }

    _moves_line(line_locs, pcur, locs, _D) {
        const order_map = [1, 0, 2]
        const shift_map = [
            [1,2,4,8,16,32,64,128,256,512],
            [0,0,0,0,0,0,0,0,0,0],
            [1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288],
        ]
        const ps_noalloc = new Uint8Array(_.BOARD_SIZE)
        const make_ps = (ps)=>{
            ps[0] = order_map[this.see(line_locs[0])+1]
            for (const i of R.range(1,_.BOARD_SIZE)) {
                const p = this.see(line_locs[i])
                ps[i] = order_map[p+1] & ((line_locs[i] == line_locs[0]) - 1)
            }
            return ps
        }
        function encode_line(ps) {
            let state_i = 0
            let base = 1
            for (const i of R.range(0,_.BOARD_SIZE)) {
                state_i += ps[i] * base
                base *= 3
            }
            return state_i
        }
        const ps = make_ps(ps_noalloc)
        const state_i = encode_line(ps)
        const move_bits = TABLE.LINE_MOVE_BITS[state_i]
        const shifts = shift_map[pcur+1]
        if (_D) {console.log(`--ps:${ps} state_i:${state_i} mb:${move_bits} shifts:${shifts}`)}
        const adds = []
        const loc_0 = line_locs[0]
        const mask_0 = shifts[0]
        if ((move_bits & mask_0) > 0) {
            if (_D) {console.log(`--[0] hit loc:${loc_0} mask:${mask_0}`)}
            adds.push(loc_0)
            locs.add(loc_0)
        }
        for (let i = 1; i < _.BOARD_SIZE; i+=1) {
            const loc = line_locs[i]
            const mask = (shifts[i]) & ((loc == loc_0) - 1)
            if ((move_bits & mask) > 0) {
                if (_D) {console.log(`--[${i}] hit loc:${loc} mask:${mask}`)}
                adds.push(loc)
                locs.add(loc)
            }
        }
        return adds
    }
    moves(pcur, _D=false, _L=null) {
        const locs = new Set()
        if (! R.isNil(_L)) {this._moves_line(TABLE.LINE_LOCS[_L], pcur, locs, _D); return [...locs]}
        for (const i in R.range(0, TABLE.LINE_LOCS.length)) {
            if (_D) {console.log(`L[${i}] = ${TABLE.LINE_LOCS[i]}`)}
            const adds = this._moves_line(TABLE.LINE_LOCS[i], pcur, locs, _D)
        }
        return [...locs]
    }

    swap(pcur, loc) {
        const arr = this._arr
        const order_map = [1, 0, 2]
        const side = order_map[(-pcur) + 1]
        const STOP = [2, 0, 1]
        function on_way(lt, i) {
            const loc = lt[i]
            const p = arr[loc]
            const p_map = order_map[p+1]
            if (((TABLE.SWAP_LOC_MASK[loc][i] * side) & (p_map & side)) == _.PIECE_EMPTY) {
                return p_map & order_map[pcur+1]
            }
            if (on_way(TABLE.SWAP_LOC_TABLE[loc], i)) {
                arr[loc] = pcur
                return true
            }
            return false
        }
        const center = TABLE.SWAP_LOC_TABLE[loc]
        for (const i_lt in center) {
            on_way(center, i_lt)
        }
        return true
    }

    down(pcur, loc, _can_move=false) {
        if (_can_move || this.can_move(pcur,loc)) {
            this._arr[loc] = pcur
            this.swap(pcur, loc)
            return true
        }
        return false
    }
}

class Record {
    constructor(step, loc, pcur) {
        this._step = step
        this._loc = loc
        this._pcur = pcur
    }
    step() {return this._step}
    loc() {return this._loc}
    pcur() {return this._pcur}
}

class State {
    constructor(_from=undefined) {
        this._board = new Board()
        this.reset(_from)
    }

    reset(_from=undefined) {
        if (R.is(State,_from)) {
            this._board.reset(_from._board)
            this._pcur = _from._pcur
            this._has_over = _from._has_over
            this._record_top = _from._record_top
        } else {
            this._board.reset()
            this._pcur = _.PIECE_BLACK
            this._has_over = -1
            this._record_top = new Record(0, -1, _.PIECE_EMPTY)
        }
        return this
    }

    pcur(){
        return this._pcur
    }

    board() {
        return this._board
    }

    has_over() {
        return (this._has_over > 0)
    }

    record_top() {
        return this._record_top
    }

    pass() {
        if (this._has_over < 1) {
            this._pcur = -(this._pcur)
            this._has_over += 1
        }
        return this
    }

    can_down(loc) {
        return ((!this.has_over()) && this._board.can_move(this._pcur, loc))
    }

    down(loc, _can_down=false) {
        if ((!this.has_over()) && this._board.down(this._pcur, loc, _can_down)) {
            const n_black = this._board.n_black()
            const n_white = this._board.n_white()
            const n_piece = (this._pcur == _.PIECE_BLACK) ? n_black : n_white
            if (n_piece >= _.BOARD_LEN) {
                this._has_over = 1
            }
            if ((n_black > 0 && n_white < 1) || (n_white > 0 && n_black < 1)) {this._has_over = 1}
            while (this._has_over < 1) {
                this._pcur = -(this._pcur)
                let candowns = this._board.moves(this._pcur)
                if (candowns.length > 0) {
                    this._has_over = -1
                    break
                }
                this._has_over += 1
            }
            this._record_top = new Record(n_black + n_white - 4, loc, -(this._pcur))
            return true
        }
        return false
    }
}

export default {
    ..._,
    Board, Record, State,
}
